package org.gpp.proj1.map.parser;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import org.gpp.proj1.map.MalformedMapFileException;
import org.gpp.proj1.map.combat.CombatMap;
import org.gpp.proj1.map.combat.Decoration;
import org.gpp.proj1.map.combat.ProceduralTexture;
import org.gpp.proj1.map.combat.SplatTexture;

import com.jme.math.Quaternion;
import com.jme.math.Vector3f;

/**
 * Reades a well formulated file of combat map
 * @author Pablo Diego
  */
public class CombatMapFileParser {

	/**
	 * @param file
	 * @return
	 * @throws IOException
	 * @throws MalformedMapFileException
	 */
	public static CombatMap parseFile(String file) throws IOException, MalformedMapFileException{
		
		// reader
		File f = new File(file);		
		BufferedReader reader = new BufferedReader(new FileReader(f));		
		String line = "";
		
		// variables to fill
		String mapHeight = null;
		float heighMultiplier = 0.0f;
		List<ProceduralTexture> textures = null;
		List<SplatTexture> splats = null;
		List<Decoration> decorations = null;
		
		// heighmap
		line = nextValidLine(reader);
		if (line != null) {
			StringTokenizer tokenizer = new StringTokenizer(line.trim(), " ");
			mapHeight = tokenizer.nextToken();
			heighMultiplier = Float.parseFloat(tokenizer.nextToken());
		}
		else {
			throw new MalformedMapFileException("Inexpected end of map file.");
		}

		// textures, splats, decorations
		while( (line = nextValidLine(reader)) != null ){
			line = line.trim();
			
			if( line.startsWith("textures") ){
				textures = readtextures(reader, getCount(line));
			}
			else if( line.startsWith("splats") ){
				splats = new ArrayList<SplatTexture>();
				readSplats(reader, splats, getCount(line));
			}
			else if( line.startsWith("decorations") ){
				decorations = new ArrayList<Decoration>();
				readDecorations(reader, decorations, getCount(line));
			}
			else{				
				throw new MalformedMapFileException("Invalid token!");
			}
		}
		reader.close();
		
		// the map itself
		return new CombatMap(mapHeight, heighMultiplier, textures, splats, decorations);
	}
	
	/**
	 * @param reader
	 * @param splats
	 * @param i 
	 * @return
	 * @throws MalformedMapFileException
	 * @throws IOException
	 */
	private static List<SplatTexture> readSplats(BufferedReader reader, List<SplatTexture> splats, int count) throws MalformedMapFileException, IOException {
		
		String line = "";
		for (int i = 1; i <= count; i++) {
		
			line = nextValidLine(reader);
			
			if( line.startsWith("splat") ){
				splats.add(readSplat(reader));
			}
			else{
				throw new MalformedMapFileException("Invalid token found!");
			}
		}
		return splats;
	}

	/**
	 * @param reader
	 * @return
	 * @throws MalformedMapFileException 
	 * @throws IOException 
	 */
	private static SplatTexture readSplat(BufferedReader reader) throws MalformedMapFileException, IOException {
		String line = "";
		
		String mask = "";
		String texture = "";
		
		while( (line = nextValidLine(reader)) != null ){
			
			if( line.startsWith("mask") ){
				mask = readValue(line);
			}
			else if( line.startsWith("texture") ){
				texture = readValue(line);
			}
			else if( line.startsWith("endsplat") ){
				//stop reading
				break;
			}
			else{
				throw new MalformedMapFileException("Invalid token found!");
			}
		}
		
		return new SplatTexture(mask, texture);
	}

	/**
	 * @param reader
	 * @param count 
	 * @return
	 * @throws MalformedMapFileException 
	 * @throws IOException 
	 */
	private static List<ProceduralTexture> readtextures(BufferedReader reader, int count) throws MalformedMapFileException, IOException {
		String line = "";
		List<ProceduralTexture> textures = new ArrayList<ProceduralTexture>();
		
		for (int i = 1; i <= count; i++) {
			
			line = nextValidLine(reader);
			
			if( line.startsWith("texture") ){
				textures.add(readTexture(reader));
			}
			else{
				throw new MalformedMapFileException("Invalid token found: " + line);
			}
		}
		return textures;
	}

	/**
	 * @param reader
	 * @return
	 * @throws MalformedMapFileException
	 * @throws NumberFormatException
	 * @throws IOException
	 */
	private static ProceduralTexture readTexture(BufferedReader reader) throws MalformedMapFileException, NumberFormatException, IOException {
		String line = "";
		
		String file = "";
		int low = -1;
		int optimal = -1;
		int high = -1;
		
		while( (line = nextValidLine(reader)) != null ){
			
			if( line.startsWith("file") ){
				file = readValue(line);
			}
			else if( line.startsWith("low") ){
				low = Integer.parseInt(readValue(line));
			}
			else if( line.startsWith("optimal") ){
				optimal = Integer.parseInt(readValue(line));
			}
			else if( line.startsWith("high") ){
				high = Integer.parseInt(readValue(line));
			}
			else if( line.startsWith("endtexture") ){
				//stop reading
				break;
			}
			else{
				throw new MalformedMapFileException("Invalid token found!");
			}
		}
		
		return new ProceduralTexture(file, low, optimal, high);
	}

	/**
	 * @param reader
	 * @param decorations
	 * @param count
	 * @return
	 * @throws IOException
	 * @throws MalformedMapFileException
	 */
	private static List<Decoration> readDecorations(BufferedReader reader, List<Decoration> decorations, int count) throws IOException, MalformedMapFileException{
		
		String line = "";
		for (int i = 1; i <= count; i++) {
			
			line = nextValidLine(reader);
			
			if( line.startsWith("decoration") ){
				
				decorations.add(readDecoration(reader));
			}
			else{
				
				throw new MalformedMapFileException("Invalid token found!");
			}
		}
		
		return decorations;
	}
	
	/**
	 * @param reader
	 * @return
	 * @throws IOException
	 * @throws MalformedMapFileException
	 */
	private static Decoration readDecoration(BufferedReader reader) throws IOException, MalformedMapFileException{
		
		String line = "";
		
		Quaternion rotation = null;
		Vector3f translation = null;
		Float scale = 0.0f;
		String modelPath = "";
		String texturePath = "";
		
		while( (line = nextValidLine(reader)) != null ){
			
			//read the rotation as a Quaternion
			if( line.startsWith("rotation") ){
				
				StringTokenizer st = new StringTokenizer(readValue(line), " ");
				float f1 = Float.parseFloat(st.nextToken());
				float f2 = Float.parseFloat(st.nextToken());
				float f3 = Float.parseFloat(st.nextToken());
				
				float[] angles = {f1, f2, f3};
				
				rotation = new Quaternion(angles);
			}
			else if( line.startsWith("translation") ){

				StringTokenizer st = new StringTokenizer(readValue(line), " ");
				float f1 = Float.parseFloat(st.nextToken());
				float f2 = Float.parseFloat(st.nextToken());
				float f3 = Float.parseFloat(st.nextToken());
				
				translation = new Vector3f(f1, f2, f3);
			}
			else if( line.startsWith("scale") ){

				scale = Float.parseFloat(readValue(line));
			}
			else if( line.startsWith("model") ){
				
				modelPath = readValue(line);
			}
			else if( line.startsWith("texture") ){
				
				texturePath = readValue(line);
			}
			else if( line.startsWith("enddecoration") ){
				//stop reading the decoration!
				break;
			}
			else{
				throw new MalformedMapFileException("Invalid token found!");
			}
		}
		
		return new Decoration(modelPath, texturePath, scale, translation, rotation);
	}
	
	/**
	 * @param reader
	 * @return
	 * @throws IOException
	 */
	private static String nextValidLine(BufferedReader reader) throws IOException{

		String line = null;
		while( (line = reader.readLine()) != null){
			//if it's a coment continue
			if( line.trim().startsWith("//") || line.trim().startsWith("#") || line.trim().equals("") ){
				continue;
			}
			else {				
				break;
			}
		}
		return line;
	}
	
	/**
	 * @param line
	 * @return
	 */
	private static String readValue(String line){
		String[] tokens = line.split("=");
		return tokens[1].trim();
	}
	
	/**
	 * @param line
	 * @return
	 */
	private static int getCount(String line) {
		StringTokenizer st = new StringTokenizer(line, "= ");
		st.nextToken();
		return Integer.parseInt(st.nextToken());
	}
}
